Alicia Sanguino Rodríguez

Ingeniería Biomédica - Procesamiento de imagen biomédica basado en IA

ACTIVIDAD 1:

Vamos a poner en práctica cuatro aspectos del procesamiento de imágenes:

Lectura de imagen y conversión a escala de grises, umbralización de una imagen siguiendo los métodos vistos en clase (y comparándolos), morfología matemática con los operadores conocidos, detección y medida de objetos dentro de la imagen.

Este último aspecto lo veremos más adelante en el curso, con lo cual, lo que haremos aquí será sencillo y guiado (pero requerirá un esfuerzo pequeño para enfrentaros a un problema que no habéis visto antes) La finalidad es sencilla. Se os dará una imagen, a color, que tiene varias tonalidades y que está pintada con círculos.

La actividad consiste en contar el número de círculos de la imagen: "image.png"

In [5]:
from google.colab.patches import cv2_imshow
import cv2

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Mostramos la imagen original y la imagen en escala de grises
    cv2_imshow(imagen)
    cv2_imshow(imagen_gris)

Se comienza cargando la imagen 'imagen.png.jpg' utilizando la biblioteca OpenCV (cv2). La carga de la imagen es un paso fundamental para cualquier procesamiento de imágenes posterior.

Se verifica si la imagen se ha cargado correctamente para evitar problemas posteriores. Si la imagen no se carga correctamente, se muestra un mensaje de error.

Luego, la imagen se convierte a una representación en escala de grises utilizando el método cv2.cvtColor(). La conversión a escala de grises es importante porque muchas técnicas de procesamiento de imágenes funcionan mejor en imágenes en escala de grises, ya que simplifica la información de color a una sola dimensión (tonalidad de gris).

Después de la conversión a escala de grises, se utilizan las funciones cv2_imshow para mostrar tanto la imagen original como la imagen en escala de grises en el entorno de Google Colab. Esto permite una inspección visual de ambas versiones de la imagen.

2.1. Umbralización con el método 'umbralización global':

In [8]:
import cv2
from google.colab.patches import cv2_imshow

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')
# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización global (ajusta el valor del umbral según tu imagen)
    umbral = 128  # Puedes ajustar este valor según tus necesidades
    _, imagen_umbralizada = cv2.threshold(imagen_gris, umbral, 255, cv2.THRESH_BINARY)

    # Mostramos la imagen umbralizada
    cv2_imshow(imagen_umbralizada)

Comenzamos cargando la imagen utilizando OpenCV para poder trabajar con ella. Como ya hemos convertido la imagen a escale de grises, podemos aplicar la umbralización global, que consiste en seleccionar un valor de umbral único para toda la imagen.

En este caso, hemos utilizado un valor fijo de 128. Este proceso convierte los píxeles de la imagen en blanco o negro según si su intensidad está por encima o por debajo del umbral.

Finalmente, hemos mostrado la imagen umbralizada para una inspección visual de cómo la umbralización global afecta a la imagen. La umbralización global es una técnica fundamental en el procesamiento de imágenes para separar objetos o regiones de interés del fondo en función de la intensidad de los píxeles. La elección del valor de umbral adecuado es esencial y depende de la naturaleza de la imagen y los resultados deseados.

2.2. Umbralización con el método 'umbralización adaptativa':

In [10]:
import cv2
from google.colab.patches import cv2_imshow

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización adaptativa (ajusta los parámetros según tus necesidades)
    umbral_adaptativo = cv2.adaptiveThreshold(imagen_gris, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

    # Mostramos la imagen umbralizada adaptativa
    cv2_imshow(umbral_adaptativo)

Primero, cargamos una imagen ('imagen.png.jpg') y la convertimos a escala de grises. La umbralización adaptativa es esencial en situaciones donde la iluminación varía significativamente en diferentes partes de la imagen. En lugar de aplicar un único valor de umbral para toda la imagen, la umbralización adaptativa calcula un valor de umbral local para cada región de la imagen, lo que permite adaptarse a las variaciones de intensidad de manera eficaz.

En este caso, hemos utilizado la función cv2.adaptiveThreshold de OpenCV, especificando el método de umbralización adaptativa (cv2.ADAPTIVE_THRESH_GAUSSIAN_C), el tamaño del bloque (11) y la constante de umbral (2). Estos parámetros pueden ajustarse según la imagen y las necesidades específicas del procesamiento. La umbralización adaptativa mejora la precisión en imágenes con iluminación no uniforme o con objetos de interés que tienen contrastes locales diferentes con el fondo.

2.3. Umbralización con el método de Otsu:

In [12]:
import cv2
from google.colab.patches import cv2_imshow

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización de Otsu
    _, umbral_otsu = cv2.threshold(imagen_gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Mostramos la imagen umbralizada con Otsu
    cv2_imshow(umbral_otsu)

Hemos comenzado cargando una imagen ('imagen.png.jpg') utilizando OpenCV y verificando si la carga fue exitosa. Luego, hemos convertido la imagen a escala de grises, ya que la umbralización de Otsu generalmente se aplica a imágenes en escala de grises.

La umbralización de Otsu es necesaria en situaciones en las que no se conoce previamente el valor de umbral óptimo para separar los objetos de interés del fondo en una imagen. En lugar de establecer manualmente un valor de umbral, Otsu calcula automáticamente el umbral óptimo al maximizar la varianza entre las dos clases de píxeles (por encima y por debajo del umbral). Esto significa que Otsu encuentra el valor de umbral que mejor separa las regiones de interés de la imagen del fondo, incluso cuando la distribución de intensidad de los píxeles es compleja o no uniforme.

El método de Otsu calcula un valor de umbral que minimiza la varianza intra-clases (varianza dentro de cada una de las dos clases de píxeles) y maximiza la varianza inter-clases (varianza entre las dos clases de píxeles). Esto resulta en una umbralización precisa y efectiva para segmentar objetos en imágenes.

3.1. Morfología matemática con 'erosión' utilizando de antemano la umbralización del método Otsu:

In [14]:
import cv2
from google.colab.patches import cv2_imshow
import numpy as np

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización de Otsu
    _, umbral_otsu = cv2.threshold(imagen_gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Operación de erosión
    kernel = np.ones((3, 3), np.uint8)  # Kernel de erosión
    imagen_erocion = cv2.erode(umbral_otsu, kernel, iterations=1)

    # Mostramos la imagen original, la imagen umbralizada con Otsu y la imagen erosionada
    cv2_imshow(imagen_erocion)

En este código, se realiza una operación de erosión utilizando un kernel de 3x3 para reducir el tamaño de los objetos blancos en la imagen binaria.

La erosión es una operación de morfología matemática que elimina pequeños detalles y ruido de la imagen binaria. Es especialmente útil para separar o reducir objetos conectados. En resumen, este código procesa la imagen original para destacar objetos de interés mediante umbralización y luego refina la imagen mediante erosión para facilitar el análisis o la detección de objetos en la imagen.

3.2. Morfología matemática con 'dilatación' utilizando de antemano la umbralización del método Otsu:

In [15]:
import cv2
from google.colab.patches import cv2_imshow
import numpy as np

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización de Otsu
    _, umbral_otsu = cv2.threshold(imagen_gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Operación de dilatación
    kernel = np.ones((3, 3), np.uint8)  # Kernel de dilatación
    imagen_dilatacion = cv2.dilate(umbral_otsu, kernel, iterations=1)

    # Mostramos la imagen dilatada
    cv2_imshow(imagen_dilatacion)

Se realiza una operación de dilatación en la imagen umbralizada utilizando cv2.dilate. La dilatación es una operación de morfología matemática que se utiliza para aumentar el tamaño de los objetos blancos en una imagen binaria. En este caso, se utiliza un kernel de 3x3 y una iteración para la dilatación. La dilatación puede ayudar a cerrar huecos en los objetos blancos y conectar componentes separados.

La dilatación es necesaria en este contexto para resaltar y realzar los objetos de interés en la imagen umbralizada, lo que puede facilitar la detección o el análisis posterior de los objetos.

3.3. Morfología matemática con 'apertura' utilizando de antemano la umbralización del método Otsu:

In [16]:
import cv2
from google.colab.patches import cv2_imshow
import numpy as np

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización de Otsu
    _, umbral_otsu = cv2.threshold(imagen_gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Operación de apertura
    kernel = np.ones((3, 3), np.uint8)  # Kernel de apertura
    imagen_apertura = cv2.morphologyEx(umbral_otsu, cv2.MORPH_OPEN, kernel)

    # Mostramos la imagen después de la apertura
    cv2_imshow(imagen_apertura)

Se realiza una operación de apertura en la imagen umbralizada utilizando cv2.morphologyEx con el flag cv2.MORPH_OPEN. La apertura es una operación morfológica que consiste en aplicar una erosión seguida de una dilatación. La erosión se utiliza para eliminar pequeños detalles y ruido de la imagen binaria, mientras que la dilatación puede ayudar a conectar componentes o separar objetos conectados.

La apertura es necesaria en este contexto para limpiar la imagen binaria resultante de la umbralización y prepararla para análisis posteriores o detección de objetos. También puede ayudar a mejorar la segmentación y la identificación de los objetos de interés en la imagen.

3.4. Morfología matemática con 'apertura' utilizando de antemano la umbralización del método Otsu:

In [17]:
import cv2
from google.colab.patches import cv2_imshow
import numpy as np

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización de Otsu
    _, umbral_otsu = cv2.threshold(imagen_gris, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # Operación de cierre
    kernel = np.ones((3, 3), np.uint8)  # Kernel de cierre
    imagen_cierre = cv2.morphologyEx(umbral_otsu, cv2.MORPH_CLOSE, kernel)

    # Mostramos la imagen después del cierre
    cv2_imshow(imagen_cierre)

Se realiza una operación de clausura en la imagen umbralizada utilizando cv2.morphologyEx con el flag cv2.MORPH_CLOSE. La clausura es una operación morfológica que consiste en aplicar una dilatación seguida de una erosión. La dilatación se utiliza para cerrar huecos y ampliar objetos, mientras que la erosión se utiliza para reducir el tamaño de los objetos y eliminar detalles pequeños.

La clausura es necesaria en este contexto para mejorar la segmentación de los objetos de interés y para unir objetos cercanos o cerrar huecos en ellos. Es especialmente útil cuando se trabaja con imágenes en las que los objetos pueden tener huecos o estar cerca unos de otros.

In [29]:
import cv2
import numpy as np

# Cargamos la imagen
imagen = cv2.imread('imagen.png.jpg')

# Verificamos si la imagen se cargó correctamente
if imagen is None:
    print('No se pudo cargar la imagen.')
else:
    # Convertimos la imagen a escala de grises
    imagen_gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)

    # Umbralización adaptativa
    umbral_adaptativo = cv2.adaptiveThreshold(imagen_gris, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

    # Operación de cierre (opcional, si es necesaria)
    kernel = np.ones((3, 3), np.uint8)
    imagen_cierre = cv2.morphologyEx(umbral_adaptativo, cv2.MORPH_CLOSE, kernel)

    # Encuentra contornos en la imagen binaria
    contornos, _ = cv2.findContours(imagen_cierre, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # Contador de círculos
    numero_de_circulos = 0

    # Procesar cada contorno encontrado
    for contorno in contornos:
        # Aproximar el contorno a una forma más simple (círculo)
        perimetro = cv2.arcLength(contorno, True)
        aproximacion = cv2.approxPolyDP(contorno, 0.005 * perimetro, True)

        # Si la aproximación tiene 16 lados (cercano a un círculo), considerarlo como círculo
        if len(aproximacion) >= 16:
            numero_de_circulos += 1
            # Puedes calcular más medidas aquí, como área o diámetro

    # Mostrar el número de círculos detectados
    print(f'Número de círculos detectados: {numero_de_circulos}')
Número de círculos detectados: 1297

En primer lugar, se ha usado la umbralización adaptativa puesto que era en la que mejor se visualizaban los círculos de la imagen. No obstante, se puede usar cualquiera de las umbralizaciones vistas.

A continuación, se utilizan contornos para encontrar las regiones conectadas en la imagen binaria, que representan los objetos de interés. Cada contorno encontrado se aproxima a una forma más simple, como un círculo, si cumple con ciertos criterios (en este caso, al menos 16 lados). Esto simplifica la forma del contorno y permite identificar objetos circulares.

Finalmente, se cuenta el número de objetos detectados (círculos) y se pueden realizar medidas adicionales, como el área o el diámetro, para cada objeto detectado. La detección y medida de objetos en una imagen es esencial en aplicaciones de procesamiento de imágenes y visión por computadora, y se utiliza para identificar y cuantificar objetos de interés en una imagen para su análisis o clasificación.

Lo importante de esta parte es que hay que definir el número dentro de la función: "aproximacion = cv2.approxPolyDP(contorno, 0.005 * perimetro, True)". Ese 0.005 lo hemos puesto nosotros, pero puede usarse cualquier otro número y variará el número de círculos en función del perímetro que estamos usando. No obstante, no sabemos cómo obtener el número ideal para sacar el número real de puntos, porque no sabemos cuanto mide cada uno.